#pragma once
#include <cassert>
#include <cstddef>

namespace krryn{
namespace core{
	template <typename _Tx>
	class refc_ptr{
	public:
		refc_ptr(_Tx* a_Pointer = 0){
			m_Counter =
				a_Pointer
					? new ref(a_Pointer)
					: 0;
		}

        template<typename _Ty>
        refc_ptr<_Ty> as(){
			// this only works as long as _Tx and _Ty are compatible types
            refc_ptr<_Ty> l_Ptr = refc_ptr<_Ty>(get());
            inc();
            return l_Ptr;
        }

		refc_ptr(const refc_ptr& a_Other){
			inc(a_Other.m_Counter);
		}

		refc_ptr& operator=(const refc_ptr& a_NewRef){
			if(this != &a_NewRef){
				dec(); // decrement old reference
				inc(a_NewRef.m_Counter);
			}
			return *this;
		}

		template<typename _Ty>
		refc_ptr& operator=(const refc_ptr<_Ty> &a_Other){
			if(this != &a_Other){
				dec();
				inc(a_Other.m_Counter);
			}

			return *this;
		}

		~refc_ptr(){
			dec();
		}

		operator bool() const{
			return m_Counter != 0;
		}

		inline _Tx* operator->() const{
			assert(m_Counter);

			return m_Counter->m_Pointer;
		}

		inline _Tx& operator*() const{
			assert(m_Counter);

			return *m_Counter->m_Pointer;
		}

		inline _Tx* get() const{
			return m_Counter ? m_Counter->m_Pointer : 0;
		}

		inline size_t count() const{
			assert(m_Counter);

			return m_Counter ? m_Counter->m_Count : 0;
		}

		inline void inc(){
			assert(m_Counter);

			m_Counter->m_Count++;
		}
	private:
		struct ref{
			explicit ref(_Tx* a_Pointer) : m_Pointer(a_Pointer), m_Count(1) {}

			_Tx* m_Pointer;
			size_t m_Count;

			~ref(){
				delete m_Pointer, m_Pointer = 0;
			}
		} *m_Counter;

		void inc(ref* a_Counter){
			m_Counter = a_Counter;

			if(a_Counter)
				a_Counter->m_Count++;
		}

		void dec(){
			if(m_Counter){
				if(!--m_Counter->m_Count){
					delete m_Counter;
				}
				m_Counter = 0;
			}
		}
	};
}
}
